package com.xiaoleilu.hutool.util;

import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import com.xiaoleilu.hutool.exceptions.UtilException;
import com.xiaoleilu.hutool.io.FileUtil;
import com.xiaoleilu.hutool.io.IoUtil;
import com.xiaoleilu.hutool.lang.Assert;

/**
 * XML工具类<br>
 * 此工具使用w3c dom工具，不需要依赖第三方包。<br>
 * 工具类封装了XML文档的创建、读取、写出和部分XML操作
 *
 * @author xiaoleilu
 */
public class XmlUtil {

    /**
     * 在XML中无效的字符 正则
     */
    public final static String INVALID_REGEX = "[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]";

    // -------------------------------------------------------------------------------------- Read
    /**
     * 读取解析XML文件
     *
     * @param file XML文件
     * @return XML文档对象
     */
    public static Document readXML(File file) {
        return org.dromara.hutool.core.xml.XmlUtil.readXml(file);
    }

    /**
     * 读取解析XML文件<br>
     * 如果给定内容以“&lt;”开头，表示这是一个XML内容，直接读取，否则按照路径处理<br>
     * 路径可以为相对路径，也可以是绝对路径，相对路径相对于ClassPath
     *
     * @param pathOrContent 内容或路径
     * @return XML文档对象
     * @since 3.0.9
     */
    public static Document readXML(String pathOrContent) {
        return org.dromara.hutool.core.xml.XmlUtil.readXml(pathOrContent);
    }

    /**
     * 读取解析XML文件<br>
     * 编码在XML中定义
     *
     * @param inputStream XML流
     * @return XML文档对象
     * @throws UtilException IO异常或转换异常
     * @since 3.0.9
     */
    public static Document readXML(InputStream inputStream) throws UtilException {
        return org.dromara.hutool.core.xml.XmlUtil.readXml(inputStream);
    }

    /**
     * 读取解析XML文件
     *
     * @param reader XML流
     * @return XML文档对象
     * @throws UtilException IO异常或转换异常
     * @since 3.0.9
     */
    public static Document readXML(Reader reader) throws UtilException {
        return org.dromara.hutool.core.xml.XmlUtil.readXml(reader);
    }

    /**
     * 读取解析XML文件<br>
     * 编码在XML中定义
     *
     * @param source {@link InputSource}
     * @return XML文档对象
     * @since 3.0.9
     */
    public static Document readXML(InputSource source) {
        return org.dromara.hutool.core.xml.XmlUtil.readXml(source,true);
    }

    /**
     * 将String类型的XML转换为XML文档
     *
     * @param xmlStr XML字符串
     * @return XML文档
     */
    public static Document parseXml(String xmlStr) {
        return org.dromara.hutool.core.xml.XmlUtil.parseXml(xmlStr);
    }

    /**
     * 从XML中读取对象
     * Reads serialized object from the XML file.
     * @param <T> 对象类型
     * @param source XML文件
     * @return 对象
     * @throws IOException IO异常
     */
    public static <T> T readObjectFromXml(File source) throws IOException {
        return org.dromara.hutool.core.xml.XmlUtil.readObjectFromXml(source);
    }

    /**
     * 从XML中读取对象
     * Reads serialized object from the XML file.
     *
     * @param <T> 对象类型
     * @param xmlStr XML内容
     * @return 对象
     * @throws IOException IO异常
     * @since 3.2.0
     */
    public static <T> T readObjectFromXml(String xmlStr) throws IOException {
        return org.dromara.hutool.core.xml.XmlUtil.readObjectFromXml(xmlStr);
    }

    /**
     * 从XML中读取对象
     * Reads serialized object from the XML file.
     *
     * @param <T> 对象类型
     * @param source {@link InputSource}
     * @return 对象
     * @throws IOException IO异常
     * @since 3.2.0
     */
    @SuppressWarnings("unchecked")
    public static <T> T readObjectFromXml(InputSource source) throws IOException {
        return org.dromara.hutool.core.xml.XmlUtil.readObjectFromXml(source);
    }

    // -------------------------------------------------------------------------------------- Write
    /**
     * 将XML文档转换为String<br>
     * 字符编码使用XML文档中的编码，获取不到则使用UTF-8
     *
     * @param doc XML文档
     * @return XML字符串
     */
    public static String toStr(Document doc) {
        return org.dromara.hutool.core.xml.XmlUtil.toStr(doc);
    }

    /**
     * 将XML文档转换为String<br>
     * 此方法会修改Document中的字符集
     *
     * @param doc XML文档
     * @param charset  自定义XML文件的编码，如果为{@code null} 读取XML文档中的编码，否则默认UTF-8
     * @return XML字符串
     */
    public static String toStr(Document doc, String charset) {
        return org.dromara.hutool.core.xml.XmlUtil.toStr(doc, Charset.forName(charset),false);
    }

    /**
     * 将XML文档转换为String<br>
     * 此方法会修改Document中的字符集
     *
     * @param doc XML文档
     * @param charset  自定义XML文件的编码，如果为{@code null} 读取XML文档中的编码，否则默认UTF-8
     * @param isPretty 是否格式化输出
     * @return XML字符串
     * @since 3.0.9
     */
    public static String toStr(Document doc, String charset, boolean isPretty) {
        return org.dromara.hutool.core.xml.XmlUtil.toStr(doc, Charset.forName(charset), isPretty);
    }

    /**
     * 将XML文档写入到文件<br>
     * 使用Document中的编码
     *
     * @param doc XML文档
     * @param absolutePath 文件绝对路径，不存在会自动创建
     */
    public static void toFile(Document doc, String absolutePath) {
        return org.dromara.hutool.core.xml.XmlUtil.toFile(doc, absolutePath);
    }

    /**
     * 将XML文档写入到文件<br>
     *
     * @param doc XML文档
     * @param path 文件路径绝对路径或相对ClassPath路径，不存在会自动创建
     * @param charset 自定义XML文件的编码，如果为{@code null} 读取XML文档中的编码，否则默认UTF-8
     */
    public static void toFile(Document doc, String path, String charset) {
        return org.dromara.hutool.core.xml.XmlUtil.toFile(doc, path, charset);
    }

    /**
     * 将XML文档写出
     * @param doc {@link Document} XML文档
     * @param writer 写出的Writer，Writer决定了输出XML的编码
     * @param isPretty 是否格式化输出
     * @since 3.0.9
     */
    public static void write(Document doc, Writer writer, boolean isPretty) {
        return org.dromara.hutool.core.xml.XmlUtil.write(doc, writer, isPretty);
    }

    // -------------------------------------------------------------------------------------- Create
    /**
     * 创建XML文档<br>
     * 创建的XML默认是utf8编码，修改编码的过程是在toStr和toFile方法里，既XML在转为文本的时候才定义编码
     *
     * @param rootElementName 根节点名称
     * @return XML文档
     */
    public static Document createXml(String rootElementName) {
        return org.dromara.hutool.core.xml.XmlUtil.createXml(rootElementName);
    }

    // -------------------------------------------------------------------------------------- Function
    /**
     * 获得XML文档根节点
     * @param doc {@link Document}
     * @return 根节点
     * @see Document#getDocumentElement()
     * @since 3.0.8
     */
    public static Element getRootElement(Document doc) {
        return org.dromara.hutool.core.xml.XmlUtil.getRootElement(doc);
    }

    /**
     * 去除XML文本中的无效字符
     *
     * @param xmlContent XML文本
     * @return 当传入为null时返回null
     */
    public static String cleanInvalid(String xmlContent) {
        return org.dromara.hutool.core.xml.XmlUtil.cleanInvalid(xmlContent);
    }

    /**
     * 根据节点名获得子节点列表
     *
     * @param element 节点
     * @param tagName 节点名，如果节点名为空（null或blank），返回所有子节点
     * @return 节点列表
     */
    public static List<Element> getElements(Element element, String tagName) {
        return org.dromara.hutool.core.xml.XmlUtil.getElements(element, tagName);
    }

    /**
     * 根据节点名获得第一个子节点
     *
     * @param element 节点
     * @param tagName 节点名
     * @return 节点
     */
    public static Element getElement(Element element, String tagName) {
        return org.dromara.hutool.core.xml.XmlUtil.getElement(element, tagName);
    }

    /**
     * 根据节点名获得第一个子节点
     *
     * @param element 节点
     * @param tagName 节点名
     * @return 节点中的值
     */
    public static String elementText(Element element, String tagName) {
        return org.dromara.hutool.core.xml.XmlUtil.elementText(element, tagName);
    }

    /**
     * 根据节点名获得第一个子节点
     *
     * @param element 节点
     * @param tagName 节点名
     * @param defaultValue 默认值
     * @return 节点中的值
     */
    public static String elementText(Element element, String tagName, String defaultValue) {
        return org.dromara.hutool.core.xml.XmlUtil.elementText(element, tagName, defaultValue);
    }

    /**
     * 将NodeList转换为Element列表
     *
     * @param nodeList NodeList
     * @return Element列表
     */
    public static List<Element> transElements(NodeList nodeList) {
        return org.dromara.hutool.core.xml.XmlUtil.transElements(nodeList);
    }

    /**
     * 将NodeList转换为Element列表<br>
     * 非Element节点将被忽略
     *
     * @param parentEle 父节点，如果指定将返回此节点的所有直接子节点，nul返回所有就节点
     * @param nodeList NodeList
     * @return Element列表
     */
    public static List<Element> transElements(Element parentEle, NodeList nodeList) {
        return org.dromara.hutool.core.xml.XmlUtil.transElements(parentEle, nodeList);
    }

    /**
     * 将可序列化的对象转换为XML写入文件，已经存在的文件将被覆盖<br>
     * Writes serializable object to a XML file. Existing file will be overwritten
     * @param <T> 对象类型
     * @param dest 目标文件
     * @param t 对象
     * @throws IOException IO异常
     */
    public static <T> void writeObjectAsXml(File dest, T t) throws IOException {
        return org.dromara.hutool.core.xml.XmlUtil.writeObjectAsXml(dest, t);
    }

    /**
     * 创建XPath<br>
     * Xpath相关文章：https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html
     *
     * @return {@link XPath}
     * @since 3.2.0
     */
    public static XPath createXPath() {
        return org.dromara.hutool.core.xml.XmlUtil.createXPath();
    }

    /**
     * 通过XPath方式读取XML节点等信息<br>
     * Xpath相关文章：https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html
     *
     * @param expression XPath表达式
     * @param source 资源，可以是Docunent、Node节点等
     * @param returnType 返回类型，{@link javax.xml.xpath.XPathConstants}
     * @return 匹配返回类型的值
     * @since 3.2.0
     */
    public static Object getByXPath(String expression, Object source, QName returnType) {
        return org.dromara.hutool.core.xml.XmlUtil.getByXPath(expression, source, returnType);
    }
    // ---------------------------------------------------------------------------------------- Private method start
    // ---------------------------------------------------------------------------------------- Private method end
}

